Cloneable的通用约定有三:
x.clone() != x
为true
。x.clone().getClass() == x.getClass()
为true
。 但这不是绝对的要求。x.clone().equals(x)
为true
。 但这也不是绝对的要求。想像下面这个ABC
类,包含了A
,B
,C
三个类型的域。copy()
方法创建一个新对象,然后逐域拷贝引用,这是一个标准的浅拷贝
,但它却满足Cloneable
接口的通用约定。
public class ABC {
A a;
B b;
C c;
public ABC(A a, B b, C c) {
this.a = a;
this.b = b;
this.c = c;
}
public ABC copy(ABC o) { // 这个copy完全执行浅拷贝,但返回对象满足Cloneable接口的约定
return new ABC(o.a, o.b, o.c);
}
}
Object类源码中的注释部分如下:
First, if the class of this object does not implement the interface {@code Cloneable}, then a code CloneNotSupportedException is thrown. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation. Note that all arrays are considered to implement the interface {@code Cloneable}
总结起来就是三点:
Clonable
接口,调用clone()
方法会抛出CloneNotSupportedException
异常。Clonable
接口的类,clone()
方法需要执行一个浅拷贝
。Clonable
接口。clone()
方法只做 浅拷贝
就可以满足Cloneable
接口的约定。 浅拷贝
都是复制对象,而不是内容。
浅拷贝
的情况,都是 深拷贝
。String
和Enum
,尤其是Enum
应该绝对禁止拷贝。Cloneable
接口。下面的代码展示了一个类实现Cloneable
接口的惯用法。
Cloneable
接口可变对象
域,递归实现Cloneable
接口这样实现的结果,也只能说尽可能地接近深拷贝
。因为其中的不可变
域还是浅拷贝
。但这样的浅拷贝
并不影响实际的使用。
public class Person implements Cloneable {
private final String name; // String is not Cloneable
private final int age;
private final Sex sex; // Enum is not Cloneable
private final PhoneNumber telephone;
// ... some other code here
@Override
public Object clone() {
try {
return (Person)super.clone();
} catch (CloneNotSupportedException e) { // cannot happen
return new RuntimeException(e);
}
}
}
public class PhoneNumber implements Cloneable {
private final short areaCode;
private final short prefix;
private final short lineNumber;
@Override
public Object clone() {
try {
return (PhoneNumber)super.clone();
} catch (CloneNotSupportedException e) { // cannot happen
return new RuntimeException(e);
}
}
}
当一个类要实现一个行为良好的clone()
方法,如果它的所有超类都递归调用了super.clone()
方法,并最终追溯到最终超级父类Object
的clone()
方法,那么Object
的clone()
方法总能复制并返回一个正确的类型。因此,这个类也可以调用super.clone()
方法就能获得一个正确的拷贝对象。
但如果中间哪一个类没有调用super.clone()
方法,而是使用了自己的构造器返回克隆的实例,那么它的所有子类就都不能获得Object
的clone()
方法提供的良好服务。而Object
的clone()
方法是行为良好的clone()
方法的有力保障。所以,尽量保持自动向上构造器调用链的畅通。
具体参考下面这个例子,Person
,Employee
,Manager
三个类层层继承。Employee
比Person
多了一个表示职位的域position
,Manager
又比Employee
多了一个表示持有公司股票份额的域stock
。这三个类的clone()
方法异常简单,都只是向上转达了对super.clone()
的调用。而最后实际执行工作的Object#clone()
能够识别出调用对象mg
运行时的实际类型,并且成功拷贝了Manager
的所有四个域。将任务交给Object#clone()
来做,事情就变得简单许多。
package com.ciaoshen.effectivejava.chapter3;
public class TestConstructor {
private static class Person implements Cloneable {
protected final String name;
protected short age;
public Person(String name, int age) {
this.name = name;
this.age = (short)age;
}
public String toString() {
return "Person[" + name + ", " + age + "]";
}
public Person clone() {
try {
return (Person)super.clone(); // 最后实际执行任务的是Object#clone()方法,它识别出实际类型为Manager,然后逐域拷贝。
} catch(CloneNotSupportedException e) {
throw new RuntimeException(e); // never happen
}
}
}
private static class Employee extends Person implements Cloneable {
protected String position;
public Employee(String name, int age, String position) {
super(name,age);
this.position = position;
}
public String toString() {
return "Employee[" + name + ", " + age + ", " + position + "]";
}
public Employee clone() {
return (Employee)super.clone();
}
}
private static class Manager extends Employee implements Cloneable {
protected int stock;
public Manager(String name, int age, String position, int stock) {
super(name,age,position);
this.stock = stock;
}
public String toString() {
return "Manager[" + name + ", " + age + ", " + position + ", " + stock + "]";
}
public Manager clone() {
return (Manager)super.clone();
}
}
public static void main(String[] args) {
Manager mg = new Manager("Ronald", 30, "Chef de project", 10000);
Manager mgCopy = mg.clone(); // 最终追溯到Object#clone()
System.out.println(mgCopy);
}
}
和静态工厂一样,为复杂对象的拷贝提供一个拷贝工厂也可以让事情变得更简单明了。
Cloneable
接口。Cloneable
接口。Cloneable
接口和clone()
方法。